package com.edfu.core.commons.service.impl;

import static com.edfu.core.commons.exceptions.CommonExceptions.CATEGORY_NAME_NOT_PRESENT;
import static com.edfu.core.commons.exceptions.CommonExceptions.CATEGORY_NOT_FOUND;
import static com.edfu.core.commons.exceptions.CommonExceptions.INTERESTS_NOTIFICATION_ID_NOT_PRESENT;
import static com.edfu.core.commons.exceptions.CommonExceptions.NULL_ARGUMENT;
import static com.edfu.core.commons.exceptions.CommonExceptions.OBJECT_ID_NOT_PRESENT;
import static com.edfu.core.commons.exceptions.CommonExceptions.OBJECT_TYPE_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.USER_NAME_NULL_OR_EMPTY;
import static com.edfu.core.testmgt.exceptions.TestExceptions.USER_NOT_FOUND;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.edfu.core.commons.beans.Category;
import com.edfu.core.commons.beans.EduObjectType;
import com.edfu.core.commons.beans.Notification;
import com.edfu.core.commons.exceptions.CategoryException;
import com.edfu.core.commons.exceptions.InvalidArgumentException;
import com.edfu.core.commons.persistence.api.ICategoryMgtPersistence;
import com.edfu.core.commons.persistence.api.IInterestsMgtPersistence;
import com.edfu.core.commons.persistence.api.INotificationPersistence;
import com.edfu.core.commons.persistence.entity.CategoryEntity;
import com.edfu.core.commons.persistence.entity.DeletedNotificationsEntity;
import com.edfu.core.commons.persistence.entity.InterestsEntity;
import com.edfu.core.commons.persistence.entity.InterestsNotificationEntity;
import com.edfu.core.commons.service.api.INotificationService;
import com.edfu.core.commons.util.CategoryMgtBeanTransformer;
import com.edfu.core.identitymgt.persistence.api.IIdentityMgtPersistence;
import com.edfu.core.identitymgt.persistence.entity.UserEntity;

@Service
public class NotificationServiceImpl implements INotificationService {
	
	private static Logger logger = LoggerFactory.getLogger(NotificationServiceImpl.class);
	
	@Autowired
	private ICategoryMgtPersistence categoryMgtPersistence;
	
	@Autowired
	private IIdentityMgtPersistence identityMgtPersistence;
	
	@Autowired
	private INotificationPersistence notificationPersistence;
	
	@Autowired
	private IInterestsMgtPersistence interestsMgtPersistence;
	
	private void validateObjectId(long objectId) {
		if (objectId == 0) {
			logger.warn("Object ID is not present");
			throw new InvalidArgumentException(OBJECT_ID_NOT_PRESENT);
		}
	}
	
	private void validateInterestsNotificationId(long interestsNotificationId) {
		if (interestsNotificationId == 0) {
			logger.warn("InterestsNotification ID is not present");
			throw new InvalidArgumentException(INTERESTS_NOTIFICATION_ID_NOT_PRESENT);
		}
	}
	
	private void validateObjectType(EduObjectType objectType) {
		if (objectType == null) {
			logger.warn("Object Type is not present");
			throw new InvalidArgumentException(OBJECT_TYPE_NOT_PRESENT);
		}
	}
	
	private void checkNull(Object obj) {
		if (obj == null) {
			throw new InvalidArgumentException(NULL_ARGUMENT);
		}
	}
	
	private UserEntity getUserEntityAndValidate(String userId) {
		
		if (StringUtils.isBlank(userId) || StringUtils.isEmpty(userId)) {
			logger.warn("UserId is Null or Empty");
			throw new InvalidArgumentException(USER_NAME_NULL_OR_EMPTY);
		}
		
		UserEntity ue = identityMgtPersistence.getUser(userId);
		
		if (ue == null) {
			logger.warn("User not found");
			throw new RuntimeException(USER_NOT_FOUND);
		}
		return ue;
	}
	
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public void addInterestsNotification(Notification notification) {
		checkNull(notification);
		addInterestsNotification(notification.getObjectId(), EduObjectType.valueOf(notification.getObjectType()), notification.getCategory());
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public void addInterestsNotification(long objectId, EduObjectType objectType, Category category) {
		validateObjectId(objectId);
		validateObjectType(objectType);
		
		checkNull(category);
		
		if (StringUtils.isBlank(category.getName()) || StringUtils.isEmpty(category.getName())) {
			logger.warn("Category name not present");
			throw new InvalidArgumentException(CATEGORY_NAME_NOT_PRESENT);
		}
		
		CategoryEntity categoryEntity = categoryMgtPersistence.getCategory(category.getName());
		
		if (categoryEntity == null) {
			logger.info("Category is not present");
			throw new CategoryException(CATEGORY_NOT_FOUND);
		}
		
		InterestsNotificationEntity interestsNotificationEntity = new InterestsNotificationEntity();
		interestsNotificationEntity.setCategory(categoryEntity);
		interestsNotificationEntity.setCreatedOn(Calendar.getInstance().getTimeInMillis());
		interestsNotificationEntity.setObjectId(objectId);
		interestsNotificationEntity.setObjectType(objectType.toString());
		
		notificationPersistence.addInterestsNotification(interestsNotificationEntity);
	}
	
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public void addInterestsNotification(long objectId, EduObjectType objectType, Set<Category> categories) {
		for (Category category : categories) {
			addInterestsNotification(objectId, objectType, category);
		}
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public void addDeletedInterestsNotification(long interestsNotificationId, String userId) {
		validateInterestsNotificationId(interestsNotificationId);
		UserEntity ue = getUserEntityAndValidate(userId);
		
		InterestsNotificationEntity interestsNotification = notificationPersistence.getInterestsNotification(interestsNotificationId);
		
		DeletedNotificationsEntity deletedNotificationsEntity = new DeletedNotificationsEntity();
		deletedNotificationsEntity.setDeletedOn(Calendar.getInstance().getTimeInMillis());
		deletedNotificationsEntity.setInterestsNotification(interestsNotification);
		deletedNotificationsEntity.setUser(ue);
		
		notificationPersistence.addDeletedInterestsNotification(deletedNotificationsEntity);		
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<Notification> getActiveNotifications(String userId) {
		UserEntity userEntity = getUserEntityAndValidate(userId);
		List<InterestsEntity> userInterestEntities = interestsMgtPersistence.getUserInterests(userEntity);
		List<Notification> notifications = new ArrayList<Notification>();
		
		if ((userInterestEntities == null ) || (userInterestEntities.size() == 0)) {
			logger.info("No interests added by the user " + userId);
			logger.info("Returning empty notifications");
			return notifications;
		}
		
		List<CategoryEntity> categoryEntities = new ArrayList<CategoryEntity>();
		for (InterestsEntity interestsEntity : userInterestEntities) {
			categoryEntities.add(interestsEntity.getCategory());
		}
		
		List<InterestsNotificationEntity> interestsNotificationEntities = notificationPersistence.getInterestsNotifications(categoryEntities);
		
		// Now check if there are any deleted notifications. Filter the deleted notifications from all category notifications
		List<DeletedNotificationsEntity> deletedNotificationEntities = notificationPersistence.getDeletedNotifications(userEntity);
		
		if (deletedNotificationEntities == null || deletedNotificationEntities.size() == 0) {
			logger.info("There are no deleted notifications for the user " + userId);
			return convertNotifications(notifications, interestsNotificationEntities);
		} else {
			logger.info("User has deleted some notifications. Filtering the deleted notifications");
			return filterAndConvertNotifications(notifications, deletedNotificationEntities, interestsNotificationEntities);
		}		
	}

	private List<Notification> filterAndConvertNotifications(
			List<Notification> notifications,
			List<DeletedNotificationsEntity> deletedNotificationEntities,
			List<InterestsNotificationEntity> interestsNotificationEntities) {
		Map<Long, InterestsNotificationEntity> interestsMap = convertNotificationToMap(interestsNotificationEntities);
		
		for (DeletedNotificationsEntity deletedNotification : deletedNotificationEntities) {
			interestsMap.remove(deletedNotification.getInterestsNotification().getId());
		}
		
		return convertNotifications(notifications, interestsMap);
	}

	private List<Notification> convertNotifications(
			List<Notification> notifications,
			Map<Long, InterestsNotificationEntity> interestsMap) {
		for (Long interestsNotificationId : interestsMap.keySet()) {
			notifications.add(convertNotification(interestsMap.get(interestsNotificationId)));
		}
		return notifications;
	}

	private Map<Long, InterestsNotificationEntity> convertNotificationToMap(
			List<InterestsNotificationEntity> interestsNotificationEntities) {
		Map<Long, InterestsNotificationEntity> interestsMap = new HashMap<Long, InterestsNotificationEntity>();
		
		for (InterestsNotificationEntity ine : interestsNotificationEntities) {
			interestsMap.put(ine.getId(), ine);
		}
		return interestsMap;
	}

	private List<Notification> convertNotifications(List<Notification> notifications, List<InterestsNotificationEntity> interestsNotificationEntities) {
		for (InterestsNotificationEntity interestsNotificationEntity : interestsNotificationEntities) {
			notifications.add(convertNotification(interestsNotificationEntity));
		}
		return notifications;
	}
	
	private Notification convertNotification(InterestsNotificationEntity interestsNotificationEntity) {
		Notification notification = new Notification();
		notification.setCategory(CategoryMgtBeanTransformer.transformCategory(interestsNotificationEntity.getCategory()));
		notification.setCreatedOn(new Date(interestsNotificationEntity.getCreatedOn()));
		notification.setNotificationId(interestsNotificationEntity.getId());
		notification.setObjectId(interestsNotificationEntity.getObjectId());
		notification.setObjectType(interestsNotificationEntity.getObjectType());
		
		return notification;
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public void deleteInterestsNotification(long objectId, EduObjectType objectType) {
		validateObjectId(objectId);
		validateObjectType(objectType);
		
		notificationPersistence.deleteInterestsNotification(objectId, objectType.toString());
	}

	@Override
	public List<Notification> getRecentContent(int size) {
		if (size == 0) {
			size = 10; //default size
		}
		
		List<InterestsNotificationEntity> interestsNotificationEntities = notificationPersistence.getRecentContent(size);
		List<Notification> notifications = new ArrayList<Notification>();
		
		if (interestsNotificationEntities != null && interestsNotificationEntities.size() > 0) {
			convertNotifications(notifications, interestsNotificationEntities);
		}
		
		return notifications;
	}

}
